package com.zhidou;

import com.zhidou.Bricks.Brick;
import com.zhidou.R;

import android.content.Context;
import android.content.res.Resources;
import android.media.MediaPlayer;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.util.AttributeSet;
import android.util.Log;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.View;
import android.widget.TextView;

public class DxBallView extends TileView {

    /**
     * Labels for the drawables that will be loaded into the TileView class
     */
    private static final int RED_STAR = 1;
    private static final int YELLOW_STAR = 2;
    private static final int GREEN_STAR = 3;
    private TextView mStatusText;
    private int mMode;
    private int mScore;
    private long mLastMove;
    private static final long mMoveDelay = 60;

    public static final int PAUSE = 0;
    public static final int READY = 1;
    public static final int RUNNING = 2;
    public static final int LOSE = 3;
    public static final int WIN = 4;

    private Ball mBall;
    private Bricks mBricks;
    private Paddle mPaddle;

    private MediaPlayer mMediaPlay;

    private static final String DXBALL_VIEW_TAG = "DxBallView";

    /**
     * Create a simple handler that we can use to cause animation to happen. We
     * set ourselves as a target and we can use the sleep() function to cause an
     * update/invalidate to occur at a later date.
     */
    private RefreshHandler mRedrawHandler = new RefreshHandler();

    class RefreshHandler extends Handler {

	@Override
	public void handleMessage(Message msg) {
	    DxBallView.this.update();
	    DxBallView.this.invalidate();
	}

	public void sleep(long delayMillis) {
	    this.removeMessages(0);
	    sendMessageDelayed(obtainMessage(0), delayMillis);
	}
    };

    public DxBallView(Context context, AttributeSet attrs, int defStyle) {
	super(context, attrs, defStyle);
	initTileView();
    }

    public DxBallView(Context context, AttributeSet attrs) {
	super(context, attrs);
	initTileView();
    }

    private void initTileView() {
	setFocusable(true);
	Resources r = this.getContext().getResources();
	resetTiles(4);
	loadTile(RED_STAR, r.getDrawable(R.drawable.redstar));
	loadTile(YELLOW_STAR, r.getDrawable(R.drawable.yellowstar));
	loadTile(GREEN_STAR, r.getDrawable(R.drawable.greenstar));

    }

    public void setMediaPlay(MediaPlayer mMediaPlay) {
	this.mMediaPlay = mMediaPlay;
    }

    public void setTextView(TextView findViewById) {
	mStatusText = findViewById;
	mStatusText.setOnTouchListener(new View.OnTouchListener() {
	    static final int MIN_DISTANCE = 20;
	    static final String logTag = "ActivitySwipeDetector";
	    private float downX, downY, upX, upY;

	    private void onRightToLeftSwipe() {
		Log.i(logTag, "RightToLeftSwipe!");
		DxBallView.this.onUp();
	    }

	    private void onLeftToRightSwipe() {
		Log.i(logTag, "LeftToRightSwipe!");
		DxBallView.this.onUp();
	    }

	    private void onTopToBottomSwipe() {
		Log.i(logTag, "onTopToBottomSwipe!");
	    }

	    private void onBottomToTopSwipe() {
		Log.i(logTag, "onBottomToTopSwipe!");

	    }

	    @Override
	    public boolean onTouch(View v, MotionEvent event) {
		switch (event.getAction()) {
		case MotionEvent.ACTION_DOWN: {
		    downX = event.getX();
		    downY = event.getY();
		    return true;
		}
		case MotionEvent.ACTION_UP: {
		    upX = event.getX();
		    upY = event.getY();

		    float deltaX = downX - upX;
		    float deltaY = downY - upY;

		    // swipe horizontal?
		    if (Math.abs(deltaX) > MIN_DISTANCE) {
			// left or right
			if (deltaX < 0) {
			    this.onLeftToRightSwipe();
			    return true;
			}
			if (deltaX > 0) {
			    this.onRightToLeftSwipe();
			    return true;
			}
		    } else {
			Log.i(logTag, "Swipe was only " + Math.abs(deltaX) + " long, need at least " + MIN_DISTANCE);
			return false; // We don't consume the event
		    }

		    // swipe vertical?
		    if (Math.abs(deltaY) > MIN_DISTANCE) {
			// top or down
			if (deltaY < 0) {
			    this.onTopToBottomSwipe();
			    return true;
			}
			if (deltaY > 0) {
			    this.onBottomToTopSwipe();
			    return true;
			}
		    } else {
			Log.i(logTag, "Swipe was only " + Math.abs(deltaX) + " long, need at least " + MIN_DISTANCE);
			return false; // We don't consume the event
		    }
		    return true;
		}
		}
		return false;
	    }
	});
    }

    /**
     * Updates the current mode of the application (RUNNING or PAUSED or the
     * like) as well as sets the visibility of textview for notification
     * 
     * @param newMode
     */
    public void setMode(int newMode) {
	int oldMode = mMode;
	mMode = newMode;

	if (newMode == RUNNING & oldMode != RUNNING) {
	    mStatusText.setVisibility(View.INVISIBLE);
	    update();
	    return;
	}

	Resources res = getContext().getResources();
	CharSequence str = "";
	if (newMode == PAUSE) {
	    str = res.getText(R.string.mode_pause);
	}
	if (newMode == READY) {
	    str = res.getText(R.string.mode_ready);
	}
	if (newMode == LOSE) {
	    str = res.getString(R.string.mode_lose_prefix) + mScore + res.getString(R.string.mode_lose_suffix);
	}
	if (newMode == WIN) {
	    str = res.getString(R.string.mode_win_prefix) + mScore + res.getString(R.string.mode_win_suffix);
	}

	mStatusText.setText(str);
	mStatusText.setVisibility(View.VISIBLE);
    }

    /**
     * Handles the basic update loop, checking to see if we are in the running
     * state, determining if a move should be made, updating the dxball and
     * pandle's location.
     */
    public void update() {
	if (mMode == RUNNING) {
	    long now = System.currentTimeMillis();
	    if (now - mLastMove > mMoveDelay) {
		clearTiles();
		updateWalls();
		updateBricks();
		updatePaddle();
		updateBall();
		mLastMove = now;
	    }
	    mRedrawHandler.sleep(mMoveDelay);
	}
    }

    private void updatePaddle() {
	if (mPaddle.checkCollide(mBall)) {
	    mMediaPlay.start();
	}
	for (int i = mPaddle.getLeft(); i <= mPaddle.getRight(); i++) {
	    setTile(DxBallView.GREEN_STAR, i, TileView.mYTileCount - 1);
	}

    }

    private void updateBricks() {
	if (mBricks.checkCollide(mBall)) {
	    mMediaPlay.start();
	    ++mScore;
	}
	if (mBricks.getBrickList().size() == 0) {
	    setMode(WIN);
	    return;
	}
	for (Brick b : mBricks.getBrickList()) {
	    // Log.d(Bricks.TAG, b.toString());
	    for (int i = b.leftX; i <= b.rightX; i++) {
		setTile(DxBallView.YELLOW_STAR, i, b.y);
	    }
	}
    }

    private void updateBall() {
	int r = mBall.move();
	if (r == 0) {
	    setMode(LOSE);
	    return;
	}
	setTile(DxBallView.RED_STAR, mBall.getX(), mBall.getY());
    }

    private void updateWalls() {
	for (int x = 0; x < mXTileCount; x++) {
	    setTile(GREEN_STAR, x, 0);
	}
	for (int y = 1; y < mYTileCount; y++) {
	    setTile(GREEN_STAR, 0, y);
	    setTile(GREEN_STAR, mXTileCount - 1, y);
	}
    }

    /*
     * Restore the state
     */
    public void restoreState(Bundle map) {
	Log.i(DXBALL_VIEW_TAG, "restoreState!");
	setMode(PAUSE);
	mScore = map.getInt("mScore");
	mBall = (Ball) map.getSerializable("mBall");
	mBricks = (Bricks) map.getSerializable("mBricks");
	mPaddle = (Paddle) map.getSerializable("mPaddle");
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
	// Log.d(DXBALL_VIEW_TAG, "eventX:" + (int) event.getX() + ", eventY:" +
	// (int) event.getY() + ",paddleLeft:" + (mXOffset + mPaddle.getLeft() *
	// mTileSize)
	// + ",paddleRight:" + (mXOffset + mPaddle.getRight() * mTileSize) +
	// "mPaddleY:" + (mYOffset + (TileView.mYTileCount) * mTileSize));
	if (mMode != RUNNING)
	    return super.onTouchEvent(event);

	int x = (int) event.getX();
	int y = (int) event.getY();
	if (y >= mYOffset + (TileView.mYTileCount - 2) * mTileSize || y <= mYOffset + (TileView.mYTileCount) * mTileSize) {
	    int mid = mXOffset + ((mPaddle.getLeft() + mPaddle.getRight()) / 2) * mTileSize;
	    if (x < mid) {
		mPaddle.moveLeft();
	    } else if (x > mid) {
		mPaddle.moveRight();
	    }
	    return true;
	}
	return super.onTouchEvent(event);
    }

    /**
     * Save game state so that the user does not lose anything if the game
     * process is killed while we are in the background.
     * 
     * @return a Bundle with this view's state
     */
    public Bundle saveState() {
	Log.i(DXBALL_VIEW_TAG, "saveState!");
	Bundle map = new Bundle();
	map.putInt("mScore", mScore);
	map.putSerializable("mBall", mBall);
	map.putSerializable("mBricks", mBricks);
	map.putSerializable("mPaddle", mPaddle);
	return map;
    }

    /*
     * handles key events in the game.
     * 
     * @see android.view.View#onKeyDown(int, android.os.KeyEvent)
     */
    @Override
    public boolean onKeyDown(int keyCode, KeyEvent msg) {

	if (keyCode == KeyEvent.KEYCODE_DPAD_UP) {
	    return onUp();
	}
	if (keyCode == KeyEvent.KEYCODE_DPAD_LEFT) {
	    mPaddle.moveLeft();
	    return (true);
	}

	if (keyCode == KeyEvent.KEYCODE_DPAD_RIGHT) {
	    mPaddle.moveRight();
	    return (true);
	}

	return super.onKeyDown(keyCode, msg);
    }

    private boolean onUp() {
	if (mMode == READY | mMode == LOSE) {
	    /*
	     * At the beginning of the game, or the end of a previous one, we
	     * should start a new game.
	     */
	    initNewGame();
	    setMode(RUNNING);
	    update();
	    return (true);
	} else if (mMode == RUNNING) {
	    setMode(PAUSE);
	    return true;
	} else if (mMode == PAUSE) {
	    setMode(RUNNING);
	    update();
	    return true;
	} else if (mMode == WIN) {
	    initNextGame();
	    setMode(RUNNING);
	    update();
	    return true;
	}
	return true;
    }

    private void initNewGame() {
	mBall = new Ball();
	mBricks = new Bricks();
	mPaddle = new Paddle(mPaddleSize);
	mScore = 0;
    }

    private void initNextGame() {
	mBall = new Ball();
	mBricks = new Bricks();
	mPaddle = new Paddle(mPaddleSize);
    }
}
